import coin.base.book.types as cointypes
from coin.exchange.bitmex.kr_rest.futures_product import BitmexFuturesProduct
from coin.exchange.okex_futures.kr_rest.futures_product import OkexFuturesProduct
from coin.exchange.huobi_futures.kr_rest.futures_product import HuobiFuturesProduct

try:
  import coin.fastfeature.feature_feed_pb2 as ffpb
except Exception:
  import fastfeature.feature_feed_pb2 as ffpb
# import pyfastfeature.feature.feature_feed_converter as converter_pybind

CoinTsideToFFTsideMap = {
    cointypes.TradeSide.TRADE_BUY_SIDE: ffpb.TRADE_BUY_SIDE,
    cointypes.TradeSide.TRADE_SELL_SIDE: ffpb.TRADE_SELL_SIDE,
    cointypes.TradeSide.TRADE_NO_SIDE: ffpb.TRADE_NO_SIDE
}


class BitmexBookFilter(object):
  def __init__(self):
    self.prev_timestamp = 0
    self.prev_bid0p = 0
    self.prev_ask0p = 0
    self.prev_bid0q = 0
    self.prev_ask0q = 0
    self.min_sample_freq = 100 * 1e6  # at least 100ms at once
    self.min_sample_qty_diff = 10000

  def on_bitmex_book(self, product, book):
    sampled = False
    if book.timestamp - self.prev_timestamp > self.min_sample_freq:
      sampled = True
    elif (self.prev_bid0p != book.bid0().price or self.prev_ask0p != book.ask0().price):
      sampled = True
    elif (abs(self.prev_bid0q - book.bid0().qty) >= self.min_sample_qty_diff
          or abs(self.prev_ask0q - book.ask0().qty) >= self.min_sample_qty_diff):
      sampled = True

    if sampled:
      self.prev_timestamp = book.timestamp
      self.prev_bid0p = book.bid0().price
      self.prev_ask0p = book.ask0().price
      self.prev_bid0q = book.bid0().qty
      self.prev_ask0q = book.ask0().qty
    return sampled


class BboFilter(object):
  def __init__(self):
    self.prev_bbo = None

  def on_book(self, product, book):
    bbo_tuple = (book.bid0().qty, book.ask0().qty, book.bid0().price, book.ask0().price)
    sampled = bbo_tuple != self.prev_bbo
    self.prev_bbo = bbo_tuple
    return sampled


class BboFilterCompact(object):
  def __init__(self):
    self.prev_bbo = None

  def on_book(self, product, book):
    bbo_tuple = (book.bid0().price, book.ask0().price)
    sampled = bbo_tuple != self.prev_bbo
    self.prev_bbo = bbo_tuple
    return sampled


class FeatureFeedConverterBase(object):
  Verbose = False

  def __init__(self,
               product_proto_map,
               product_ticksize_map,
               level_override_map,
               bitmex_bbo_compress,
               okex_trade_min_qty):
    self._product_ticksize_map = product_ticksize_map
    # could be per-product, but quantumizing will make it not useful
    self._ppmap = product_proto_map
    self._pimap = None
    self._level_override_map = level_override_map

    if bitmex_bbo_compress == 2:
      self._bbfilter = BboFilterCompact()
    else:
      self._bbfilter = BboFilter()

    if okex_trade_min_qty:
      self._okex_trade_min_qty = okex_trade_min_qty
    else:
      self._okex_trade_min_qty = None

    self._bitmex_bbo_compress = bitmex_bbo_compress

  def register_symbols(self, feature_dumper, another_dumper=None):
    assert self._pimap is None
    self._pimap = {}
    for product, pproduct in self._ppmap.items():
      idx = feature_dumper.RegisterAndGetSymbolIndex(pproduct)
      self._pimap[product] = idx
      if another_dumper is not None:
        assert self._pimap[product] == another_dumper.RegisterAndGetSymbolIndex(pproduct)

  def update_book(self, product, book, fastfeature_obj):
    if book.bid0() is None or book.ask0() is None:
      return False
    if self._bitmex_bbo_compress and isinstance(product, BitmexFuturesProduct):
      if not self._bbfilter.on_book(product, book):
        # do not update book.
        return False
    self.update_book_impl(product, book, fastfeature_obj)
    return True

  def update_books(self, product, book, fastfeature_objs):
    if self._bitmex_bbo_compress and isinstance(product, BitmexFuturesProduct):
      if not self._bbfilter.on_book(product, book):
        # do not update book.
        return False
    for fastfeature_obj in fastfeature_objs:
      self.update_book_impl(product, book, fastfeature_obj)
    return True

  def update_book_impl(self, product, book, fastfeature_obj):
    raise NotImplementedError()

  def update_trade(self, product, trade, fastfeature_obj):
    if self._okex_trade_min_qty is not None:
      if (isinstance(product, OkexFuturesProduct) or isinstance(product, HuobiFuturesProduct)):
        if not self._okex_trade_min_qty < trade.qty:
          return False
    fastfeature_obj.UpdateTrade(self._pimap[product],
                                trade.timestamp,
                                0,
                                self._product_ticksize_map[product],
                                trade.price,
                                trade.qty,
                                CoinTsideToFFTsideMap[trade.side],
                                False if product.exchange == "Bitflyer" else trade.has_more_trade)
    return True

  def trigger(self, product, fastfeature_obj):
    return fastfeature_obj.Trigger(self._pimap[product])

  def get_index(self, product):
    return self._pimap[product]


class FeatureFeedConverter(FeatureFeedConverterBase):
  Verbose = False

  def update_book_impl(self, product, book, fastfeature_obj):
    max_level = self._level_override_map[product]
    fastfeature_obj.UpdateBook(self._pimap[product],
                               book.timestamp,
                               0,
                               self._product_ticksize_map[product],
                               max_level,
                               book.get_bid_array(max_level),
                               book.get_ask_array(max_level))


class FeatureFeedCacheConverter(FeatureFeedConverterBase):
  Verbose = False

  def update_book_impl(self, product, book, fastfeature_obj):
    max_level = self._level_override_map[product]
    fastfeature_obj.UpdateBook(self._pimap[product],
                               book.timestamp,
                               0,
                               self._product_ticksize_map[product],
                               max_level,
                               book.get_bid_array(max_level),
                               book.get_ask_array(max_level))


class FeatureArchiveFeedConverter(FeatureFeedConverter):
  Verbose = True
